from fastapi import FastAPI, HTTPException, Depends
import uvicorn
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel, Field
import time
import socket
import threading
from typing import Dict, Optional, List
import json
import redis
import hashlib
import uuid
from datetime import datetime
from fastapi.staticfiles import StaticFiles

# Redis connection
redis_client = redis.Redis(
    host='106.15.130.147',
    port=6379,
    password='redis123',
    db=0,
    decode_responses=True
)

app = FastAPI(
    title="基于RFID的药品追溯防伪系统",
    description="用于管理药品信息的API服务，支持基于RFID的药品追溯和防伪",
    version="1.0.0",
    docs_url="/docs",
    redoc_url="/redoc"
)

# CORS configuration
app.add_middleware(
    CORSMiddleware,
    allow_origins=["http://106.15.130.147:5173"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# ---------- 数据模型 ----------
class MedicineBase(BaseModel):
    name: str = Field(..., description="药品名称")
    spec: str = Field(..., description="药品规格")
    manufacturer: str = Field(..., description="生产厂家")

class MedicineCreate(MedicineBase):
    batch: str = Field(..., description="生产批次")
    production_date: str = Field(..., description="生产日期 (YYYYMMDD)")
    expiry_date: str = Field(..., description="有效期至 (YYYYMMDD)")

class MedicineInfo(MedicineBase):
    rfid_id: str = Field(..., description="RFID标识符 (16位数字)")
    batch: str = Field(..., description="生产批次")
    production_date: str = Field(..., description="生产日期")
    expiry_date: str = Field(..., description="有效期至")
    created_at: str = Field(..., description="信息录入时间")
    verification_hash: str = Field(..., description="防伪验证哈希")
    
class MedicineQuery(BaseModel):
    rfid_id: str = Field(..., description="RFID标识符 (16位数字)")

class MedicineTraceEvent(BaseModel):
    rfid_id: str = Field(..., description="RFID标识符")
    event_type: str = Field(..., description="事件类型: production, storage, transport, sale, verification")
    location: str = Field(..., description="事件地点")
    operator: str = Field(..., description="操作人员")
    notes: Optional[str] = Field(None, description="备注信息")

class MedicineVerification(BaseModel):
    rfid_id: str = Field(..., description="RFID标识符")
    verification_code: Optional[str] = Field(None, description="验证码(如果有)")

# ---------- 药品信息处理函数 ----------
def generate_verification_hash(rfid_id: str) -> str:
    """生成药品防伪哈希"""
    salt = str(uuid.uuid4())
    redis_client.set(f"salt:{rfid_id}", salt)
    return hashlib.sha256(f"{rfid_id}:{salt}".encode()).hexdigest()

def verify_medicine(rfid_id: str, verification_code: Optional[str] = None) -> bool:
    """验证药品真伪"""
    # 检查药品是否存在
    if not redis_client.exists(f"medicine:{rfid_id}"):
        return False
    
    # 如果是简单验证，只检查药品是否在系统中注册
    if not verification_code:
        return True
    
    # 如果提供了验证码，进行严格验证
    stored_hash = redis_client.hget(f"medicine:{rfid_id}", "verification_hash")
    salt = redis_client.get(f"salt:{rfid_id}")
    if not salt:
        return False
    
    calculated_hash = hashlib.sha256(f"{verification_code}:{salt}".encode()).hexdigest()
    return calculated_hash == stored_hash

def record_trace_event(event: MedicineTraceEvent):
    """记录药品追溯事件"""
    event_id = f"{event.rfid_id}:{int(time.time())}"
    event_data = {
        "rfid_id": event.rfid_id,
        "event_type": event.event_type,
        "location": event.location,
        "operator": event.operator,
        "notes": event.notes or "",
        "timestamp": datetime.now().isoformat()
    }
    # 使用Redis列表存储追溯事件
    redis_client.lpush(f"trace:{event.rfid_id}", json.dumps(event_data))
    # 添加到全局事件流中
    redis_client.lpush("trace:all", json.dumps(event_data))
    return event_id

# ---------- API接口 ----------
@app.post("/api/medicines/", 
          response_model=MedicineInfo,
          summary="添加药品信息",
          tags=["药品管理"])
async def add_medicine(medicine: MedicineCreate):
    """
    添加新的药品信息到数据库，自动生成RFID标识符
    
    - 药品名称、规格、生产厂家等信息
    - 自动生成RFID标识和防伪验证码
    - 录入时间不超过2秒
    """
    # 生成16位数字RFID ID
    rfid_id = ''.join([str(uuid.uuid4().int)[:16].zfill(16)])[0:16]
    
    # 生成防伪验证哈希
    verification_hash = generate_verification_hash(rfid_id)
    
    # 创建药品信息
    medicine_data = {
        "name": medicine.name,
        "spec": medicine.spec,
        "manufacturer": medicine.manufacturer,
        "batch": medicine.batch,
        "production_date": medicine.production_date,
        "expiry_date": medicine.expiry_date,
        "created_at": datetime.now().isoformat(),
        "verification_hash": verification_hash
    }
    
    # 存储到Redis
    redis_client.hset(f"medicine:{rfid_id}", mapping=medicine_data)
    
    # 添加到索引
    redis_client.sadd("medicines:all", rfid_id)
    redis_client.sadd(f"medicines:manufacturer:{medicine.manufacturer}", rfid_id)
    redis_client.sadd(f"medicines:batch:{medicine.batch}", rfid_id)
    
    # 记录生产事件
    event = MedicineTraceEvent(
        rfid_id=rfid_id,
        event_type="production",
        location=medicine.manufacturer,
        operator="system",
        notes=f"初始生产记录，批次: {medicine.batch}"
    )
    record_trace_event(event)
    
    # 返回完整信息
    return {**medicine_data, "rfid_id": rfid_id}

@app.post("/api/medicines/query/",
          summary="查询药品信息",
          tags=["药品查询"],
          response_description="返回完整的药品信息")
async def query_medicine(query: MedicineQuery):
    """
    根据RFID标识符查询详细信息
    
    - 支持扫描RFID标签快速查询
    - 查询响应时间不超过3秒
    """
    rfid_id = query.rfid_id
    if len(rfid_id) != 16 or not rfid_id.isdigit():
        raise HTTPException(400, "RFID标识符必须是16位数字")
    
    # 从Redis查询
    medicine_data = redis_client.hgetall(f"medicine:{rfid_id}")
    
    if not medicine_data:
        raise HTTPException(404, "药品未找到")
    
    # 记录查询事件
    event = MedicineTraceEvent(
        rfid_id=rfid_id,
        event_type="verification",
        location="API查询",
        operator="system",
        notes="API查询请求"
    )
    record_trace_event(event)
    
    return {**medicine_data, "rfid_id": rfid_id}

@app.get("/api/medicines/{rfid_id}",
         summary="获取药品信息",
         tags=["药品查询"])
async def get_medicine(rfid_id: str):
    """通过RFID标识符获取单个药品信息"""
    if len(rfid_id) != 16 or not rfid_id.isdigit():
        raise HTTPException(400, "RFID标识符必须是16位数字")
    
    # 从Redis查询
    medicine_data = redis_client.hgetall(f"medicine:{rfid_id}")
    
    if not medicine_data:
        raise HTTPException(404, "药品未找到")
    
    return {**medicine_data, "rfid_id": rfid_id}

@app.get("/api/medicines/",
         summary="获取所有药品ID",
         tags=["药品查询"])
async def list_medicines():
    """返回所有已存储的药品RFID标识符列表"""
    rfid_ids = list(redis_client.smembers("medicines:all"))
    return {"rfid_ids": rfid_ids}

@app.post("/api/trace/",
          summary="记录追溯事件",
          tags=["药品追溯"])
async def add_trace_event(event: MedicineTraceEvent):
    """
    记录药品在供应链中的追溯事件
    
    - 支持生产、入库、运输、销售等各环节的记录
    - 每次记录会自动添加时间戳
    """
    rfid_id = event.rfid_id
    if len(rfid_id) != 16 or not rfid_id.isdigit():
        raise HTTPException(400, "RFID标识符必须是16位数字")
    
    # 检查药品是否存在
    if not redis_client.exists(f"medicine:{rfid_id}"):
        raise HTTPException(404, "药品未找到")
    
    # 记录追溯事件
    event_id = record_trace_event(event)
    
    return {"event_id": event_id, "status": "recorded"}

@app.get("/api/trace/{rfid_id}",
         summary="获取药品追溯历史",
         tags=["药品追溯"])
async def get_trace_history(rfid_id: str):
    """
    获取药品的完整追溯历史
    
    - 返回药品从生产到当前的所有记录事件
    - 查询响应时间不超过3秒
    """
    if len(rfid_id) != 16 or not rfid_id.isdigit():
        raise HTTPException(400, "RFID标识符必须是16位数字")
    
    # 检查药品是否存在
    if not redis_client.exists(f"medicine:{rfid_id}"):
        raise HTTPException(404, "药品未找到")
    
    # 获取追溯历史
    trace_events = redis_client.lrange(f"trace:{rfid_id}", 0, -1)
    events = [json.loads(event) for event in trace_events]
    
    return {"rfid_id": rfid_id, "events": events}

@app.post("/api/verify/",
          summary="药品防伪验证",
          tags=["防伪系统"])
async def verify_medicine_api(verification: MedicineVerification):
    """
    验证药品真伪
    
    - 利用RFID标签的唯一性进行验证
    - 验证响应时间不超过1秒
    """
    rfid_id = verification.rfid_id
    if len(rfid_id) != 16 or not rfid_id.isdigit():
        raise HTTPException(400, "RFID标识符必须是16位数字")
    
    # 验证药品
    start_time = time.time()
    is_genuine = verify_medicine(rfid_id, verification.verification_code)
    verification_time = time.time() - start_time
    
    # 记录验证事件
    event = MedicineTraceEvent(
        rfid_id=rfid_id,
        event_type="verification",
        location="API验证",
        operator="system",
        notes=f"防伪验证: {'通过' if is_genuine else '失败'}"
    )
    record_trace_event(event)
    
    if not is_genuine:
        return {
            "is_genuine": False,
            "message": "药品验证失败，可能是假冒产品",
            "verification_time": verification_time
        }
    
    # 获取药品信息
    medicine_data = redis_client.hgetall(f"medicine:{rfid_id}")
    
    return {
        "is_genuine": True,
        "message": "药品验证成功，是正品",
        "verification_time": verification_time,
        "medicine_info": {**medicine_data, "rfid_id": rfid_id}
    }

@app.get("/api/stats",
         summary="系统统计信息",
         tags=["系统管理"])
async def get_system_stats():
    """获取系统统计信息，包括药品总数、验证次数等"""
    total_medicines = redis_client.scard("medicines:all")
    total_events = redis_client.llen("trace:all")
    
    # 获取最近的事件统计
    recent_events = []
    events = redis_client.lrange("trace:all", 0, 9)  # 最近10条
    for event in events:
        recent_events.append(json.loads(event))
    
    # 事件类型统计
    event_types = {}
    for event in recent_events:
        event_type = event["event_type"]
        event_types[event_type] = event_types.get(event_type, 0) + 1
    
    return {
        "total_medicines": total_medicines,
        "total_events": total_events,
        "recent_events": recent_events,
        "event_types": event_types
    }

# UDP服务器处理RFID扫描器请求
class UDPServer:
    def __init__(self, host: str = "0.0.0.0", port: int = 8888):
        self.host = host
        self.port = port
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        self.sock.bind((host, port))
        self.running = False
    
    def handle_request(self, data: bytes, addr: tuple):
        """处理ESP32的UDP请求"""
        try:
            # 解析RFID标识符 (前16字节)
            rfid_id = data[:16].decode().strip()
            
            # 检查Redis中是否存在该药品
            medicine_data = redis_client.hgetall(f"medicine:{rfid_id}")
            
            if medicine_data:
                # 记录RFID扫描事件
                event_data = {
                    "rfid_id": rfid_id,
                    "event_type": "scan",
                    "location": "UDP扫描器",
                    "operator": "esp32",
                    "notes": "RFID扫描器读取",
                    "timestamp": datetime.now().isoformat()
                }
                redis_client.lpush(f"trace:{rfid_id}", json.dumps(event_data))
                
                # 返回药品信息
                response = json.dumps({**medicine_data, "rfid_id": rfid_id}).encode()
            else:
                response = b'{"error": "Medicine not found"}'
            
            self.sock.sendto(response, addr)
            
        except Exception as e:
            print(f"UDP处理错误: {e}")
            self.sock.sendto(b'{"error": "Processing error"}', addr)
    
    def start(self):
        self.running = True
        print(f"UDP服务已启动 {self.host}:{self.port}")
        
        while self.running:
            try:
                data, addr = self.sock.recvfrom(256)
                threading.Thread(target=self.handle_request, args=(data, addr)).start()
            except Exception as e:
                print(f"UDP服务错误: {e}")
    
    def stop(self):
        self.running = False
        self.sock.close()

# 初始化Redis数据
def init_redis():
    """初始化Redis数据，如果为空的话"""
    if redis_client.scard("medicines:all") == 0:
        # 添加一些示例数据
        example_data = [
            {
                "name": "阿司匹林",
                "spec": "100mg*30片",
                "manufacturer": "北京制药厂",
                "batch": "20230101",
                "production_date": "20230101",
                "expiry_date": "20251231"
            },
            {
                "name": "头孢克肟",
                "spec": "50mg*12粒",
                "manufacturer": "上海药业",
                "batch": "20230215",
                "production_date": "20230215",
                "expiry_date": "20241231"
            }
        ]
        
        for medicine in example_data:
            # 生成16位数字RFID ID
            rfid_id = ''.join([str(uuid.uuid4().int)[:16].zfill(16)])[0:16]
            
            # 生成防伪验证哈希
            verification_hash = generate_verification_hash(rfid_id)
            
            # 创建药品信息
            medicine_data = {
                **medicine,
                "created_at": datetime.now().isoformat(),
                "verification_hash": verification_hash
            }
            
            # 存储到Redis
            redis_client.hset(f"medicine:{rfid_id}", mapping=medicine_data)
            
            # 添加到索引
            redis_client.sadd("medicines:all", rfid_id)
            redis_client.sadd(f"medicines:manufacturer:{medicine['manufacturer']}", rfid_id)
            redis_client.sadd(f"medicines:batch:{medicine['batch']}", rfid_id)
            
            # 记录生产事件
            event_data = {
                "rfid_id": rfid_id,
                "event_type": "production",
                "location": medicine["manufacturer"],
                "operator": "system",
                "notes": f"初始生产记录，批次: {medicine['batch']}",
                "timestamp": datetime.now().isoformat()
            }
            redis_client.lpush(f"trace:{rfid_id}", json.dumps(event_data))
            redis_client.lpush("trace:all", json.dumps(event_data))
        
        print(f"已初始化{len(example_data)}个示例药品数据")

def run_server():
    # 初始化Redis数据
    try:
        init_redis()
        print("Redis数据初始化完成")
    except Exception as e:
        print(f"Redis初始化错误: {e}")
    
    # 启动UDP服务
    udp_server = UDPServer()
    udp_thread = threading.Thread(target=udp_server.start)
    udp_thread.daemon = True
    udp_thread.start()
    
    # 挂载静态文件服务
    try:
        app.mount("/", StaticFiles(directory="frontend/dist", html=True), name="frontend")
        print("前端静态文件已挂载")
    except Exception as e:
        print(f"前端静态文件挂载失败: {e}")
    
    # 配置uvicorn
    config = uvicorn.Config(
        app,
        host="0.0.0.0",
        port=8000,
        reload=False,
        workers=1,
        access_log=True
    )
    server = uvicorn.Server(config)
    
    try:
        print("服务已启动，访问 http://localhost:8000/ 使用系统，或访问 http://localhost:8000/docs 查看API文档")
        server.run()
    except KeyboardInterrupt:
        udp_server.stop()
        print("服务已停止")

if __name__ == "__main__":
    run_server()